package com.modelo.dao.comun;

import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.MatchMode;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;

import com.comun.filtro.FiltroLista;
import com.comun.utils.Utils;
import com.modelo.dao.utils.UtilsDAO;

/**
 * Clase abstracta en donde colocamos los metodos comunes de los DAOs.
 * 
 * @author David
 * 
 * @param <T>
 */
public abstract class HibernateBaseDAO<T> extends HibernateDaoSupport implements BaseDAO<T> {
	private final Logger logger = LogManager.getLogger(HibernateBaseDAO.class.getName());

	private int total;

	public HibernateBaseDAO() {
	}

	/**
	 * Constructor de la clase, debemos pasarle la session.
	 * 
	 * @param sessionFactory
	 */
	@Autowired
	public HibernateBaseDAO(SessionFactory sessionFactory) {
		super.setSessionFactory(sessionFactory);
	}

	private void addCriterion(Criteria query, List<Criterion> listaCriterion) {
		if (!Utils.isEmpty(listaCriterion)) {
			for (Criterion criterion : listaCriterion) {
				query.add(criterion);
			}
		}
	}

	private void addFetchModes(Criteria query, List<CriteriaFetchMode> listaFetchModes) {
		for (CriteriaFetchMode criteriaFetchMode : listaFetchModes) {
			query.setFetchMode(criteriaFetchMode.getName(), criteriaFetchMode.getFetchMode());
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void delete(T modelObject) {
		logger.debug("Delete :: Inicio.");
		// Debemos hacerlo de esta forma debido a que si utilizamos la
		// siguiente:
		// getHibernateTemplate().delete(persistentInstance);
		// nos da el siguiente error:
		// org.hibernate.HibernateException: Illegal attempt to associate a
		// collection with two open sessions
		// Es debido a que intentamos borrar un objeto que esta cogido en otra
		// sesion.
		// por lo que tenemos que hacer es traernoslo de la sesion que hay y
		// borrarlo.
		Session session = getSession(false);
		UtilsDAO<T> utilsDAO = new UtilsDAO<T>();

		T entidad = (T) session.load(modelObject.getClass(), utilsDAO.reflectionGetId(modelObject));
		session.delete(entidad);
		logger.debug("Delete :: Fin.");
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<T> findAll(Class<T> type) {
		String queryString = "from " + type.getCanonicalName() + " model";
		return Collections.checkedList(getSession().createQuery(queryString).list(), type);
	}

	@SuppressWarnings("unchecked")
	public List<T> findByFiltro(Class<T> type, FiltroBaseDAO filtro) {
		Criteria query = getSession().createCriteria(type.getCanonicalName());

		if (!Utils.isEmpty(filtro.getFetchModes())) {
			addFetchModes(query, filtro.getFetchModes());
		}

		if (!Utils.isEmpty(filtro.getListaCriterions())) {
			addCriterion(query, filtro.getListaCriterions());
		}

		return query.list();
	}

	@SuppressWarnings("unchecked")
	@Override
	public T findById(Class<T> type, Serializable id) {
		return (T) getHibernateTemplate().get(type.getCanonicalName(), id);
	}

	@Override
	@SuppressWarnings("unchecked")
	public List<T> findByListId(Class<T> type, List<Integer> listaIds) {

		Criteria query = getSession().createCriteria(type.getCanonicalName());

		query.add(Restrictions.isNull("fechaBaja"));
		query.add(Restrictions.in("id", listaIds));
		query.addOrder(Order.asc("nombre"));

		return query.list();
	}

	/**
	 * Metodo que obliga a guardar en base de datos todo lo que hay en el
	 * contexto.
	 */
	public void flush() {
		getHibernateTemplate().flush();
	}

	@SuppressWarnings("unchecked")
	@Override
	// si unimos los filtros ya estariamos acoplando las capas de dao con
	// service.
	public List<T> getActivos(FiltroLista filtro, FiltroBaseDAO filtroDAO, Class<T> type) {
		// log.debug("getActivos Documento  ");
		try {
			Criteria query = getSession().createCriteria(type.getCanonicalName());
			if (!Utils.isEmpty(filtroDAO.getFetchModes())) {
				addFetchModes(query, filtroDAO.getFetchModes());
			}

			if (!Utils.isEmpty(filtroDAO.getListaCriterions())) {
				addCriterion(query, filtroDAO.getListaCriterions());
			}

			query.add(Restrictions.isNull("fechaBaja"));
			if (filtro != null) {
				if (filtro.getFilters() != null && !filtro.getFilters().isEmpty()) {
					for (Iterator<String> it = filtro.getFilters().keySet().iterator(); it
							.hasNext();) {
						String filterProperty = it.next();
						String filterValue = filtro.getFilters().get(filterProperty);
						query.add(Restrictions.ilike(filterProperty, filterValue,
								MatchMode.ANYWHERE));
					}
					filtro.setObtenerTotal(true);
				}

				if (filtro.isObtenerTotal()) {
					setTotal(query.list().size());
				}

				if (filtro.getOrder() != null) {
					query.addOrder(filtro.getOrder());
				}

				if (filtro.getFilters() != null) {
					query.setFirstResult(filtro.getFirst());
				}

				if (filtro.getPageSize() != null) {
					query.setMaxResults(filtro.getPageSize());
				}
			}

			return query.list();
		} catch (RuntimeException re) {
			// log.error("find by property name failed", re);
			throw re;
		}
	}

	@Override
	public int getTotal() {
		return total;
	}

	@Override
	public void save(T modelObject) {
		getSession().save(modelObject);
	}

	protected void setTotal(int total) {
		this.total = total;
	}

	@Override
	public void update(T modelObject) {
		getSession().update(modelObject);
	}
}
